#include <stdio.h>
#include <mpi.h>

#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_vector_complex.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_fft_complex.h>

#define NUM_ROWS_A 12 //rows of input [A]
#define NUM_COLUMNS_A 12 //columns of input [A]
#define NUM_ROWS_B 12 //rows of input [B]
#define NUM_COLUMNS_B 12 //columns of input [B]
#define MASTER_TO_SLAVE_TAG 1 //tag for messages sent from master to slaves
#define SLAVE_TO_MASTER_TAG 4 //tag for messages sent from slaves to master

void makeAB(); //makes the [A] and [B] matrixes
void printArray(); //print the content of output matrix [C];

gsl_vector* v;
gsl_vector* r;
gsl_matrix* A;

int rank; //process rank
int size; //number of processes
int i, j, k; //helper variables
double start_time; //hold start time
double end_time; // hold end time

MPI_Status status; // store status of a MPI_Recv
MPI_Request request; //capture request of a MPI_Isend

#define N 100

int main(int argc, char *argv[])
{
	int j = 0;
	MPI_DOUBLE tmp[N];
	unsigned int info[N];
	
	v = gsl_vector_alloc(N);
	
	for ( j = 0 ; j < N ; j++ )
	{
		gsl_vector_set(v, j, j%13);
	}

	r = gsl_vector_alloc(N);
	
	A = gsl_matrix_alloc(N, N);
	
	MPI_Init(&argc, &argv); //initialize MPI operations
	MPI_Comm_rank(MPI_COMM_WORLD, &rank); //get the rank
	MPI_Comm_size(MPI_COMM_WORLD, &size); //get number of processes
	
	/* master initializes work*/
	if (rank == 0)
	{
		makeAB();
		start_time = MPI_Wtime();
		for (i = 1; i < size; i++)
		{
            for ( k = 0 ; k < N ; k++ )
			{
				tmp[k] = gsl_matrix_get(mat, j, k);
			}
			
			info[i] = j;
			
			MPI_Isend(tmp, N, MPI_DOUBLE, i, MASTER_TO_SLAVE_TAG + 2, MPI_COMM_WORLD, &request);
			j++;
		}
	}
	
	if (rank > 0)
	{
		double tmp[N];
		double num = 0;
		
		MPI_Recv(tmp, N, MPI_DOUBLE, 0, MASTER_TO_SLAVE_TAG + 2, MPI_COMM_WORLD, &status);
		for (i = 0; i < N; i++)
		{
            num = num + tmp[i]*v[i];
		}
		
		//send back the low bound first without blocking, to the master
		MPI_Isend(&num, 1, MPI_DOUBLE, 0, SLAVE_TO_MASTER_TAG + 2, MPI_COMM_WORLD, &request);
	}
	
	/* master gathers processed work*/
	if (rank == 0)
	{
		double num;
		for (i = 1; i < size; i++)
		{        
			MPI_Recv(&num, 1, MPI_DOUBLE, i, SLAVE_TO_MASTER_TAG + 2, MPI_COMM_WORLD, &status);
			gsl_matrix_set(r, info[i], num);
		}
		
		end_time = MPI_Wtime();
		printf("\nRunning Time = %f\n\n", end_time - start_time);
		printArray();
	}
	MPI_Finalize(); //finalize MPI operations
	return 0;
}

void makeAB()
{
	/* Define o vetor v */
	
	gsl_vector_set_zero(r);
	
	/* Define a matriz A */
	for ( i = 0 ; i < N ; i++ )
	{
		for ( j = 0 ; j < N ; j++ )
		{
			gsl_matrix_set(A, i, j, i + j);
		}
	}
}

void printArray()
{
    for (i = 0; i < N; i++)
	{
		printf("%14.6f\n", gsl_matrix_get(r, i));
	}
}